package cn.itcast.zt.luaLock;

import cn.hutool.core.util.StrUtil;
import cn.itcast.zt.utils.InetAddressUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author zhangtian105
 * @ClassName RedisDistributeLockImpl.java
 * @projectName Redis
 * @Description 分布式锁 https://zhuanlan.zhihu.com/p/298225105
 * @createTime 2020年11月18日 14:40:00
 */
@Component(value = "redisDistributeLockImpl")
public class RedisDistributeLockImpl implements DistributeLock {
    /*
     * 默认过期时间，单位毫秒
     * */
    private static final int DEFAULT_EXPIRE_TIME_MILLISECONDS= 1000;

    /**
     * 默认加锁Sleep时间 单位：毫秒
     * */
    private static final int DEFAULT_RETRY_FIXED_TIME = 5;

    /**
     * 默认的加锁浮动时间区间 单位：毫秒
     * */
    private static final int DEFAULT_RETRY_TIME_RANGE = 10;

    /**
     * 默认的加锁重试次数
     * */
    private static final int DEFAULT_RETRY_COUNT = 0;

    @Autowired
    @Qualifier(value = "stringValueOperations")
    private ValueOperations<String, String> lockOperations ;

    @Autowired
    private RedisUtil redisUtil ;

    @Autowired
    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor ;

    @Override
    public boolean trylock(String lockName) {
        return trylock(lockName, InetAddressUtil.getLocalServerIp() + "_" + Thread.currentThread().getId());
    }

    @Override
    public boolean trylock(String lockName, int expireTime) {
        return trylock(lockName,InetAddressUtil.getLocalServerIp()+"_" + Thread.currentThread().getId(), expireTime);
    }
    @Override
    public boolean trylock(String lockName, String requestID) {
        return trylock(lockName,requestID, DEFAULT_EXPIRE_TIME_MILLISECONDS);
    }

    @Override
    public boolean trylock(String lockName, String requestID, int expireTimeMillisec) {
        return lockOperations.setIfAbsent(lockName, requestID, expireTimeMillisec, TimeUnit.MILLISECONDS) ;
    }

    @Override
    public boolean lock(String lockName) {
        return lock(lockName,InetAddressUtil.getLocalServerIp() + "_" + Thread.currentThread().getId());
    }

    @Override
    public boolean lock(String lockName, String requestID) {
        return lock(lockName, requestID, DEFAULT_EXPIRE_TIME_MILLISECONDS);
    }

    @Override
    public boolean lock(String lockName, int expireTimeMillisec) {
        return lock(lockName,InetAddressUtil.getLocalServerIp() + "_" + Thread.currentThread().getId(), expireTimeMillisec);
    }

    @Override
    public boolean lock(String lockName, int expireTimeMillisec, int retryCount) {
        return lock(lockName,InetAddressUtil.getLocalServerIp() + "_" + Thread.currentThread().getId(), expireTimeMillisec, retryCount);
    }

    @Override
    public boolean lock(String lockName, String requestID, int expireTimeMillisec) {
        return lock(lockName, requestID, expireTimeMillisec, DEFAULT_RETRY_COUNT);
    }

    //带重试次数限制的，也就是前面
    @Override
    public boolean lock(String lockName, String requestID, int expireTime, int retryCount)  {
        Supplier<Boolean> supplier = ()->{
            if(trylock(lockName,requestID, expireTime)){
                return true;
            }

            ThreadLocalRandom random = ThreadLocalRandom.current();
            try {
                Thread.sleep(DEFAULT_RETRY_FIXED_TIME + random.nextInt(DEFAULT_RETRY_TIME_RANGE));
            } catch (InterruptedException e) {
                return false;
            }

            return false;
        };

        if(retryCount <= 0){ // 没有次数的话，占用CPU循环  知道拿到锁为止
            while(true){
                if(supplier.get()){ // 取得锁
                    return true;
                }
            }
        }else{
            for (int i = 0; i< retryCount; i++){ // 超过次数则直接循环结束  返回false
                if(supplier.get()){ // 取得锁
                    return true;
                }
            }
        }

        return false;
    }

    // 带续租时间，每次0.75倍的增加
    private void startLeaseExpireTimeTask(String lockName, String requestID, int expireTimeMillisec){
        scheduledThreadPoolExecutor.scheduleAtFixedRate(() ->{
            try {
                Long addTimeStatus = redisUtil.leaseTime(Collections.singletonList(lockName), requestID, String.valueOf(expireTimeMillisec * 3 / 4)) ;

                // System.out.println("续命中......"+addTimeStatus);

                if (addTimeStatus == 0L) {
                    scheduledThreadPoolExecutor.shutdown();
                }
            }catch (Exception ex) {
                scheduledThreadPoolExecutor.shutdown();
            }
        }, 0 , 1, TimeUnit.SECONDS) ; // 定时时间可以修改  频率不要太高  和过期时间做个取舍这种的时间执行即可
    }

    @Override
    public boolean lockWithLease(String lockName, String requestID, int expireTimeMillisec) {
        //不可重入的锁，所以包括自身也只能进来一次
        if(trylock(lockName, requestID, expireTimeMillisec)){
            startLeaseExpireTimeTask(lockName, requestID, expireTimeMillisec);

            return true;
        }

        return false;
    }

    //解锁，还是lua方式
    @Override
    public boolean unLock(String lockName, String requestID) {

        if (StrUtil.isBlank(requestID)) {
            requestID = InetAddressUtil.getLocalServerIp() + "_" + Thread.currentThread().getId() ;
        }

        Long result = redisUtil.unlock(Collections.singletonList(lockName), requestID) ;
        return result == 1L;
    }
}
